<?php

namespace Fluency\Utils;

/**
 * @package Fluency\Utils
 * @author  Ignas Rudaitis
 * @version 2.0.0
 */
class AnnotationReader
{
    const ANNOTATION_PATTERN = '/\*\s*(?<name>[\S]*)\s*=\s*(?<value>.+?)\s*(?=\*)/s';
    const ANNOTATION_LEVEL_SEPARATOR = '.';

    private $annotations;

    /**
     * The constructor.
     *
     * @param string $string a string to extract annotations from
     */
    public function __construct($string)
    {
        $this->parseAnnotations($string);
    }

    private function parseAnnotations($string)
    {
        preg_match_all(static::ANNOTATION_PATTERN, $string, $matches, PREG_SET_ORDER);
        foreach ($matches as $annotation) {
            $name  = $annotation["name"];
            $json  = $annotation["value"];
            $value = json_decode($json, true);
            $error = json_last_error();
            if ($error != JSON_ERROR_NONE) {
                $message = $this->getJsonErrorMessage($error);
                throw new \LogicException("Annotation $json is malformed JSON ($message)", $error);
            }
            $this->annotations[$name] = $value;
        }
    }

    private function getJsonErrorMessage($code)
    {
        $errorMessages = array(
            JSON_ERROR_NONE      => "no errors",
            JSON_ERROR_SYNTAX    => "syntax error",
            JSON_ERROR_DEPTH     => "nesting too deep",
            JSON_ERROR_CTRL_CHAR => "control character error",
            @JSON_ERROR_UTF8     => "malformed UTF-8 character"
        );
        return @$errorMessages[$code] ?: "unknown error";
    }

    public function getAllAnnotations()
    {
        return $this->annotations;
    }

    private function normalizeName($name)
    {
        return join(static::ANNOTATION_LEVEL_SEPARATOR, (array) $name);
    }

    public function getAnnotation($name)
    {
        return @$this->annotations[$this->normalizeName($name)];
    }

    public function hasAnnotation($name)
    {
        return isset($this->annotations[$this->normalizeName($name)]);
    }

    public function getSubAnnotations($parent)
    {
        $parent = $this->normalizeName($parent) . static::ANNOTATION_LEVEL_SEPARATOR;
        $length  = strlen($parent);
        $matches = array();
        foreach ($this->annotations as $key => $value) {
            if (substr($key, 0, $length) == $parent) {
                $matches[substr($key, $length)] = $value;
            }
        }
        return $matches;
    }
}
